package mint.modules.generator.expression;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

import org.antlr.runtime.tree.Tree;

import mint.MintLexer;
import mint.modules.generator.CounterManager;
import mint.modules.generator.Scope;
import mint.modules.symbol.ClassSymbol;
import mint.modules.symbol.FieldSymbol;
import mint.modules.symbol.MethodSymbol;
import mint.modules.symbol.Symbol;
import mint.modules.symbol.SymbolType;
import mint.modules.type.Type;
import mint.modules.type.TypeId;

public class Selector extends Expression {
	private String member;
	private Expression expr;
	private ArrayList<Expression> arguments;
	private ArrayList<String> errors;
	private int line;
	private Type type, prevType;
	private String leftEmit;

	public Selector(Scope theParent, ExpressionType exprType, Tree selectorTree) {
		super(theParent, exprType);
		Tree child = selectorTree.getChild(0);
		
		errors = new ArrayList<String>();
		line = selectorTree.getLine();
		arguments = null;
		type = null;
		leftEmit = "";
		
		if (child.getType() == MintLexer.ARRAYACCESS)
		{
			member = null;
			expr = new ExprNode(theParent, ExpressionType.Right, child.getChild(0));			
		} else
		{
			member = child.getChild(0).getText();
			expr = null;
			
			if (selectorTree.getChildCount() > 1)
			{
				Tree argumentTree = selectorTree.getChild(1).getChild(0);
				
				arguments = new ArrayList<Expression>();
				if (argumentTree != null)
					for (int i = 0; i < argumentTree.getChildCount(); i++)
					{
						arguments.add(new ExprNode(theParent, ExpressionType.Right, argumentTree.getChild(i)));
						CounterManager.getInstance().incrementParameter();
					}
			}
		}
		
	}
	
	public void testSemantic(Type previousType)
	{
		type = previousType;
		prevType = previousType;
		testSemantic();
	}

	@Override
	public void testSemantic() {
		if (type.getTypeId() == TypeId.Primitive)
		{
			errors.add(line + ": invalid operators on primitive type");
		}
		else if (expr != null) //Es un acceso a array
		{
			expr.testSemantic();
			
			if (!(type.getTypeId() != TypeId.ObjectArray || type.getTypeId() != TypeId.PrimitiveArray))
				errors.add(line + ": array access just can be used on array types");
			
			if (!expr.getType().equals(Type.intType))
				errors.add(line + ": array accessor mus't be int type");
			else
			{
				type = type.reduceIndex();
				/*if (symbolType.equals(Type.booleanArrayType))
					type = Type.booleanType;
				else if (symbolType.equals(Type.floatArrayType))
					type = Type.floatType;
				else
					type = Type.intType;*/
			}
		}
		else
		{
			Symbol classSymbol = searchSymbol(type.getName());
			
			if (classSymbol == null)
				errors.add(line + ": Error raro al buscar la clase");
			else
			{
				if (classSymbol.getSymbolType() != SymbolType.Class)
					errors.add(line + ": Error raro al buscar la clase");
				else
				{
					ClassSymbol theClass = (ClassSymbol)classSymbol;
					Symbol memberSymbol = theClass.getClassNode().searchSymbol(member, getInnerClass(), false);
					
					if (arguments != null)
					{
						MethodSymbol methodSymbol = (MethodSymbol)memberSymbol;
						
						if (getExpressionType() == ExpressionType.Left || getExpressionType() == ExpressionType.RightLeft)
							errors.add(line + ": method invocation must be on the right side of a assignation");
						if (memberSymbol.getSymbolType() != SymbolType.Method)
							errors.add(line + ": member " + member + " isn't a method");
						
						for(Expression expr : arguments)
							expr.testSemantic();
						
						if (methodSymbol.getParameterListSize() != arguments.size())
							errors.add(line + ": " + methodSymbol.getName() + " constructor take " + 
										methodSymbol.getParameterListSize() + " but received " +
										arguments.size() + " arguments");
						else
						{
							for (int i = 0; i < methodSymbol.getParameterListSize(); i++)
							{
								if (methodSymbol.getParameter(i).getType().equals(Type.floatType))
								{
									if(!(arguments.get(i).getType().equals(Type.intType) || arguments.get(i).getType().equals(Type.floatType)))
										errors.add(line + ": " + methodSymbol.getName() + " " + i + 
												"th constructor argument expected type: " + methodSymbol.getParameter(i).getType().getCompleteName()
												+ " received type: " + arguments.get(i).getType().getCompleteName());
								} else	if (!methodSymbol.getParameter(i).getType().equals(arguments.get(i).getType()))
									if(!arguments.get(i).getType().equals(Type.objectNullType))
										errors.add(line + ": " + methodSymbol.getName() + " " + i + 
											"th constructor argument expected type: " + methodSymbol.getParameter(i).getType().getCompleteName()
											+ " received type: " + arguments.get(i).getType().getCompleteName());
							}
						}						
					}
					
					if (memberSymbol instanceof FieldSymbol)
						type = ((FieldSymbol) memberSymbol).getType();
					else if (memberSymbol instanceof MethodSymbol)
						type = ((MethodSymbol) memberSymbol).getType();
					
				}
			}
		}

	}

	@Override
	public List<String> getSemanticErrors() {
		if (expr != null)
			errors.addAll(expr.getSemanticErrors());
		if (arguments != null)
			for(Expression expr : arguments)
				errors.addAll(expr.getSemanticErrors());
		return errors;
	}

	@Override
	public void emit(Formatter output) {
		if (getExpressionType() == ExpressionType.Right)
		{
			if (expr != null)
			{
				expr.emit(output);
				if (type.equals(Type.intType) || type.equals(Type.booleanType))
					output.format("iaload\n");
				else if (type.equals(Type.floatType))
					output.format("faload\n");
				else
					output.format("aaload\n");
			}
			else
			{
				if (arguments == null)
				{
					output.format("getfield %s/%s %s\n", prevType.getName(), member, type.emit());
				} else
				{
					ClassSymbol theClass = (ClassSymbol)searchSymbol(prevType.getName());					
					MethodSymbol method = (MethodSymbol)theClass.getClassNode().searchSymbol(member, getInnerClass(), false);
					
					int i = 0;
					
					for (Expression expr : arguments)
					{
						expr.emit(output);
						if (method.getParameter(i).getType().equals(Type.floatType))
							if(expr.getType().equals(Type.intType))
								output.format("i2f\n");
					}
					output.format("invokevirtual %s/%s(", prevType.getName(), member);
					for (i = 0; i < method.getParameterListSize(); i++)
						output.format("%s", method.getParameter(i).getType().emit());
					output.format(")%s\n", method.getType().emit());
				}
			}
		} else if (getExpressionType() == ExpressionType.Left || getExpressionType() == ExpressionType.RightLeft)
		{
			if (expr != null)
			{
				expr.emit(output);
				if (type.equals(Type.intType) || type.equals(Type.booleanType))
					leftEmit = String.format("iastore\n");
				else if (type.equals(Type.floatType))
					leftEmit = String.format("fastore\n");
				else
					leftEmit = String.format("aastore\n");
			}
			else
			{
				leftEmit = String.format("putfield %s/%s %s\n", prevType.getName(), member, type.emit());
			}
		}
	}

	@Override
	public int getLine() {
		return line;
	}

	@Override
	public void leftEmit(Formatter output) {
		output.format("%s\n", leftEmit);
	}

	@Override
	public Type getType() {
		return type;
	}

}
